/*******************************************************************************
 * Copyright (c) 2012-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.commons.lang;

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Helper for converting memory size to human readable representation and back, e.g 12K, 12kB, 1M, etc.
 *
 * @author andrew00x
 */
public class Size {

    /**
     * Convert memory to human readable representation, e.g. 1 kB
     *
     * @param sizeInBytes
     *         size in bytes
     * @return memory in human readable format
     * @throws java.lang.IllegalArgumentException
     *         if {@code sizeInBytes} is negative
     */
    public static String toHumanSize(long sizeInBytes) {
        if (sizeInBytes < 0) {
            throw new IllegalArgumentException(String.format("Negative size: %d", sizeInBytes));
        }
        if (sizeInBytes < K) {
            return String.format("%d B", sizeInBytes);
        }
        float size = 0.0f;
        String suffix = "PB";
        for (int i = 0, l = SIZE_UNITS.length; i < l; i++) {
            Pair<Long, String> sizeUnit = SIZE_UNITS[i];
            if (sizeInBytes >= sizeUnit.first) {
                size = (float)sizeInBytes / sizeUnit.first;
                suffix = sizeUnit.second;
                break;
            }
        }
        return String.format((size % 1.0f == 0) ? "%.0f %s" : "%.1f %s", size, suffix);
    }

    /**
     * Parse human readable size string to long size in bytes.
     *
     * @param humanSize
     *         human readable size string
     * @return long size in bytes
     * @throws IllegalArgumentException
     *         if {@code humanSize} has incorrect format
     */
    public static long parseSize(String humanSize) {
        return parseAndConvertToBytes(humanSize);
    }

    /**
     * Parse human readable size string to long size in megabytes.
     *
     * @param humanSize
     *         human readable size string
     * @return long size in megabytes
     * @throws IllegalArgumentException
     *         if {@code humanSize} has incorrect format
     */
    public static long parseSizeToMegabytes(String humanSize) {
        return parseAndConvertToBytes(humanSize) / M;
    }

    private static final long K = 1024;
    private static final long M = K * K;
    private static final long G = M * K;
    private static final long T = G * K;
    private static final long P = T * K;

    @SuppressWarnings("unchecked")
    private static Pair<Long, String>[] SIZE_UNITS = new Pair[]{
            Pair.of(P, "PB"),
            Pair.of(T, "TB"),
            Pair.of(G, "GB"),
            Pair.of(M, "MB"),
            Pair.of(K, "kB")
    };

    private static final Pattern HUMAN_SIZE_PATTERN = Pattern.compile("^([0-9]*(\\.[0-9]+)?)\\s*(\\S+)?$");

    private static long parseAndConvertToBytes(String sizeString) {
        final Matcher matcher;
        if ((matcher = HUMAN_SIZE_PATTERN.matcher(sizeString)).matches()) {
            final float size = Float.parseFloat(matcher.group(1));
            final String suffix = matcher.group(3);
            if (suffix == null) {
                return (long)size;
            }
            final String suffixL = suffix.toLowerCase(Locale.ENGLISH);
            switch (suffixL) {
                case "b":
                    return (long)(size);
                case "k":
                case "kb":
                case "kib":
                    return (long)(size * K);
                case "m":
                case "mb":
                case "mib":
                    return (long)(size * M);
                case "g":
                case "gb":
                case "gib":
                    return (long)(size * G);
                case "t":
                case "tb":
                case "tib":
                    return (long)(size * T);
                case "p":
                case "pb":
                case "pib":
                    return (long)(size * P);
            }
        }
        throw new IllegalArgumentException("Invalid size: " + sizeString);
    }

    private Size() {
    }
}
